﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.IO;

namespace MachineLearning {
    class NetworkState {

        public enum PostProcess {
            None = 0,
            ThresholdToBoolean = 1,
            RoundToDecimal = 2,
            WinnerTakesAll = 3,
        }

        NeuralNetworkInterface.updateStatusEventHandler finishedProcessingEvent;

        BatchNetworkAdapter _bat = null;
        public BatchNetworkAdapter DataAdaptor {
            get { return _bat; }
        }

        BatchNetworkAdapter _batchValidator = null;

        BatchClassifier _netI = null;
        public BatchClassifier NetworkInterface {
            get { return _netI; }
        }

        PostProcess _postProcessOption = PostProcess.None;
        public PostProcess PostProcessOption {
            get { return _postProcessOption; }
            set { _postProcessOption = value; }
        }

        Stopwatch _processTimer = null;
        public Stopwatch ProcessTimer {
            get { return _processTimer; }
        }

        int[] _hiddenUnits = null;
        public int[] HiddenUnits {
            get { return _hiddenUnits; }
            set { _hiddenUnits = value; }
        }

        private double _prevError = 0.0;
        private double _currentError = 0.0;
        private long _prevTime = 0;
        private long _currentTime = 0;

        private double _learningRate = 0.3;
        public double LearningRate {
            get { return _learningRate; }
            set { _learningRate = value; }
        }

        private double _learningMomentum = 0.0;
        public double LearningMomentum {
            get { return _learningMomentum; }
            set { _learningMomentum = value; }
        }

        private int _trainingEpochs = 1;
        public int TrainingEpochs {
            get { return _trainingEpochs; }
            set { _trainingEpochs = value; }
        }

        bool _finishedProcessing = false;
        public bool FinishedProcessing {
            get { return _finishedProcessing; }
        }

        public bool NetworkRunning {
            get { return _netI.Running; }
        }

        public NetworkState() {
            _processTimer = new Stopwatch();
            //assign the finished processing function to the event
            finishedProcessingEvent += finishedProcessing;
        }

        //train the ANN, dont call unless you initialize parameters
        public void trainNetwork() {
            _finishedProcessing = false;
            _processTimer.Start();
            if (_bat.OpMode == NeuralNetworkDataAdapter.OperationMode.Learn) {
                _netI.trainNetwork(_trainingEpochs, _hiddenUnits
                    , _learningRate, _learningMomentum);
                _netI.setNetworkName(_bat.DataSetName);
            } else _finishedProcessing = true;
         
        }

        //do batch classification, must have a network interface allready set up
        public void classifyInstances() {
            if (_netI != null)
                _netI.classifyInstances(_bat.Instances);
        }

        public void validateInstances() {
            if (_netI != null && _netI.ValidationInstances != null)
                _netI.validateNetwork();
        }

        //do batch classification, must have a network interface allready set up
        public void instantiateNetworkFromFile(string fileName) {
            resetNetwork();
            _netI.newNetworkFromFile(fileName);
            _hiddenUnits = new int[_netI.ANN.Neurons.Count];
            for (int i = 0; i < _hiddenUnits.Length; i++)
                _hiddenUnits[i] = _netI.ANN.Neurons[i].Count;
        }

        //gets the mean squared error and sets up the derivative calculation
        public double getMSE() {
            _prevError = _currentError;
            _prevTime = _currentTime;
            _currentError = _netI.getMeanSquaredError();
            _currentTime = _processTimer.ElapsedMilliseconds;
            return _currentError;
        }

        //gets the mean squared error and sets up the derivative calculation
        public double getValidationMSE() {
            _prevError = _currentError;
            _prevTime = _currentTime;
            _currentError = _netI.getValidationMeanSquaredError();
            _currentTime = _processTimer.ElapsedMilliseconds;
            return _currentError;
        }

        //calculates the first derivative of the mean squared error
        public double getDerivMSE() {
            return ((_currentError - _prevError) / (double)(_currentTime - _prevTime)) * 100000.0;
        }

        //gets the current epoch during training
        public int getCurrentEpoch() {
            return _netI.getCurrentEpoch();
        }

        //new a network interface, classify or train
        public void initNetworkAdapter(string fileName) {
            //set current working directory to the directory where the descriptor file is
            Directory.SetCurrentDirectory(fileName.Substring(0, fileName.LastIndexOf("\\")));
            //these options will become further differentiated once more data source
            //adaptors have bben implemented
            BatchNetworkAdapter bna = new TabNetworkAdapter(fileName);
            if (bna.OpMode == NeuralNetworkDataAdapter.OperationMode.Validate) {
                _batchValidator = bna;
                if(_bat != null)
                    _netI = new BatchNetworkTrainer(_bat.Conditioner, (TrainingExample[])_bat.Instances
                        , (TrainingExample[])_batchValidator.Instances);
            } else {
                _bat = bna;
                if (_bat.OpMode == NeuralNetworkDataAdapter.OperationMode.Learn)
                    _netI = new BatchNetworkTrainer(_bat.Conditioner, (TrainingExample[])_bat.Instances);
                else {
                    _netI = new BatchClassifier(_bat.Conditioner, _bat.Instances);
                }
            }
            //subscribe to the finished processing event in the network interface
            if (_netI != null && _netI.finishedProcessingEvent == null)
                _netI.finishedProcessingEvent += finishedProcessing;
        }

        //invoked by the finished processing event
        private void finishedProcessing() {
            _finishedProcessing = true;
            _processTimer.Stop();
            _processTimer.Reset();
        }

        //get the ANN output vectors via the network interface
        public double[][] getOutputVectors() {
            return _netI.FinalOutputs;
        }

        //stop the network, stops classification and training
        public void stopNetwork() {
            if(_netI != null) _netI.Running = false;
        }
        
        //reset the ANN
        public void resetNetwork() {
            if (_bat.OpMode == NeuralNetworkDataAdapter.OperationMode.Learn) {
                _netI = new BatchNetworkTrainer(_bat.Conditioner, (TrainingExample[])_bat.Instances);
                _netI.finishedProcessingEvent += finishedProcessing;
            }

        }

    }
}
